#include "matrix.h"
#include "matrixthread.h"
#include <iostream>
using namespace std;

int Matrix::create(char * filename, int row) {
    QFile outfile(filename);
    if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        cerr << "open " << filename << " failed" << endl;
        return -1;
    }
    QTextStream outstream(& outfile);
    for (int r = 0; r < row; r++) {
        for (int c = 0; c < row; c++) {
            outstream << rand() % 100 << " ";
        }
        outstream << endl;
    }
    outfile.close();
    return 0;
}

int Matrix::allocateMemory(int n) {
    numRow = n;
    elements = new int *[n];
    if (elements == NULL) {
        cerr << "memory allocation fail" << endl;
        return -1;
    }
    for (int r = 0; r < n; r++) {
        elements[r] = new int [n];
        if (elements[r] == NULL) {
            cerr << "memory allocation fail" << endl;
            return -1;
        }
    }
    return 0;
}

void Matrix::assignRow(int r, QString & oneLine) {
    QStringList qsl = oneLine.split(" ");
    for (int c = 0; c < numRow; c++) {
        elements[r][c] = qsl[c].toInt();
    }
}

Matrix::Matrix(int n) {
    allocateMemory(n);
}

Matrix::Matrix(char * filename) {
    QFile infile(filename);
    if (!infile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        cerr << "open " << filename << " failed" << endl;
        return;
    }
    QTextStream instream(&infile);
    QString oneLine = instream.readLine();
    int n = oneLine.count(" ");
    if (n <= 0) {
        cerr << "wrong matrix size" << endl;
        return;
    } else {
        // cout << "matrix size: " << n << endl;
    }
    if (allocateMemory(n) == 0) {
        int r = 0;
        assignRow(r, oneLine);
        for (int r = 1; r < n; r++) {
            oneLine = instream.readLine();
            assignRow(r, oneLine);
        }
    }
    infile.close();
}

void Matrix::copyElement(const Matrix & orig) {
    if (allocateMemory(orig.numRow) != 0) {
        return;
    }
    for (int r = 0; r < numRow; r++) {
        for (int c = 0; c < numRow; c++) {
            elements[r][c] = orig.elements[r][c];
        }
    }
}

Matrix::Matrix(const Matrix & orig) {
    copyElement(orig);
}

Matrix & Matrix::operator =(const Matrix & orig) {
    if (this != &orig) {
        // cannot use "delete this" because it will also release the
        // space for numRow
        for (int r = 0; r < numRow; r++) {
            delete [] elements[r];
        }
        delete [] elements;
        copyElement(orig);
    }
    return * this;
}

Matrix::~Matrix() {
    if (elements) {
        for (int r = 0; r < numRow; r++) {
            delete [] elements[r];
        }
        delete [] elements;
    }
}

Matrix * Matrix::multiply(Matrix * B, int thread) {
    if (numRow != B -> numRow) {
        cerr << "different dimensions" << endl;
        return 0;
    }
    if (thread > numRow) {
        thread = numRow;
    }
    Matrix * C = new Matrix(numRow);
    if (thread == 1) {
        for (int r = 0; r < numRow; r++) {
            int *ap = elements[r];
            for (int c = 0; c < numRow; c++) {
                C -> elements[r][c] = 0;
                for (int k = 0; k < numRow; k++) {
                    C -> elements[r][c] += ap[k] * (B -> elements[k][c]);
                }
            }
        }
    } else {
        try {
            MatrixThread **t = new MatrixThread*[thread];
            int r = 0;
            for (int i = 0; i < thread; i++) {
                t[i] = new MatrixThread(r,thread, numRow, this, B, C);
                t[i]->start();
                r++;
                if (r == numRow) {
                    break;
                }
            }
            for (int j = 0; j < thread; j++) {
                t[j]->wait();
            }
        } catch (exception e) {
            this -> multiply(B, thread - 1);
        }
    }
    return C;
}

void Matrix::print(char * filename) {
    QFile outfile(filename);
    if (!outfile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        cerr << "open " << filename << " failed" << endl;
        return;
    }
    QTextStream outstream(& outfile);
    for (int r = 0; r < numRow; r++) {
        for (int c = 0; c < numRow; c++) {
            outstream << elements[r][c] << " ";
        }
        outstream << endl;
    }
    outfile.close();
}